/*-------------------------------------------------------------------------
 *
 * pl_funcs.c		- PL/pgSQL 的杂项函数
 *			  过程语言
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/pl/plpgsql/src/pl_funcs.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "plpgsql.h"
#include "utils/memutils.h"

/* ----------
 * 命名空间处理的本地变量
 *
 * 命名空间结构实际上形成一棵树，其中只有一条线性
 * 列表或“链”（从最年轻的项目到根）可以从
 * 任何一个 plpgsql 语句访问。在函数的初始解析过程中，ns_top
 * 指向来自当前正在解析的块的最年轻项目。 然而，我们存储整个树，
 * 因为在运行时我们需要访问相关于任何一个语句的链。
 *
 * 命名空间链中的块边界由 PLPGSQL_NSTYPE_LABEL
 * 项标记。
 * ----------
 */
static PLpgSQL_nsitem *ns_top = NULL;


/* ----------
 * plpgsql_ns_init			初始化新函数的命名空间处理
 * ----------
 */
void plpgsql_ns_init(void)
{
	ns_top = NULL;
}


/* ----------
 * plpgsql_ns_push			创建一个新的命名空间级别
 * ----------
 */
void plpgsql_ns_push(const char *fc_label, PLpgSQL_label_type fc_label_type)
{
	if (fc_label == NULL)
		fc_label = "";
	plpgsql_ns_additem(PLPGSQL_NSTYPE_LABEL, (int) fc_label_type, fc_label);
}


/* ----------
 * plpgsql_ns_pop			弹出条目回到（包括）最后一个标签
 * ----------
 */
void plpgsql_ns_pop(void)
{
	Assert(ns_top != NULL);
	while (ns_top->itemtype != PLPGSQL_NSTYPE_LABEL)
		ns_top = ns_top->prev;
	ns_top = ns_top->prev;
}


/* ----------
 * plpgsql_ns_top			获取当前命名空间链的末端
 * ----------
 */
PLpgSQL_nsitem * plpgsql_ns_top(void)
{
	return ns_top;
}


/* ----------
 * plpgsql_ns_additem		将项目添加到当前命名空间链
 * ----------
 */
void plpgsql_ns_additem(PLpgSQL_nsitem_type fc_itemtype, int fc_itemno, const char *fc_name)
{
	PLpgSQL_nsitem *fc_nse;

	Assert(fc_name != NULL);
	/* 第一个添加的项目必须是一个标签 */
	Assert(ns_top != NULL || fc_itemtype == PLPGSQL_NSTYPE_LABEL);

	fc_nse = palloc(offsetof(PLpgSQL_nsitem, name) + strlen(fc_name) + 1);
	fc_nse->itemtype = fc_itemtype;
	fc_nse->itemno = fc_itemno;
	fc_nse->prev = ns_top;
	strcpy(fc_nse->name, fc_name);
	ns_top = fc_nse;
}


/* ----------
 * plpgsql_ns_lookup		在给定的命名空间链中查找标识符
 *
 * 请注意，这仅搜索变量，而不是标签。
 *
 * 如果 localmode 为 true，则仅搜索最顶层块级别。
 *
 * name1 不能为空。 如果解析一个少于三个组件的名称，
 * 则将 NULL 传递给 name2 和/或 name3。
 *
 * 如果 names_used 不是 NULL, 则 *names_used 接收匹配的名称数量：
 * 如果没有匹配，则为 0；如果 name1 匹配一个未限定的变量名称，则为 1；
 * 如果 name1 和 name2 匹配一个块标签 + 变量名称，则为 2。
 *
 * 请注意，name3 从来没有直接与任何东西进行匹配。 然而，如果它
 * 不是 NULL，我们将无视对标量变量的限定匹配。
 * 同样地，如果 name2 不是 NULL，我们将无视对标量变量的
 * 未限定匹配。
 * ----------
 */
PLpgSQL_nsitem * plpgsql_ns_lookup(PLpgSQL_nsitem *fc_ns_cur, bool fc_localmode,
				  const char *fc_name1, const char *fc_name2, const char *fc_name3,
				  int *fc_names_used)
{
	/* 外部循环在命名空间链中的每个块级别迭代一次 */
	while (fc_ns_cur != NULL)
	{
		PLpgSQL_nsitem *fc_nsitem;

		/* 检查该级别以匹配变量名称的未限定匹配 */
		for (fc_nsitem = fc_ns_cur;
			 fc_nsitem->itemtype != PLPGSQL_NSTYPE_LABEL;
			 fc_nsitem = fc_nsitem->prev)
		{
			if (strcmp(fc_nsitem->name, fc_name1) == 0)
			{
				if (fc_name2 == NULL ||
					fc_nsitem->itemtype != PLPGSQL_NSTYPE_VAR)
				{
					if (fc_names_used)
						*fc_names_used = 1;
					return fc_nsitem;
				}
			}
		}

		/* 检查该级别以匹配变量名称的限定匹配 */
		if (fc_name2 != NULL &&
			strcmp(fc_nsitem->name, fc_name1) == 0)
		{
			for (fc_nsitem = fc_ns_cur;
				 fc_nsitem->itemtype != PLPGSQL_NSTYPE_LABEL;
				 fc_nsitem = fc_nsitem->prev)
			{
				if (strcmp(fc_nsitem->name, fc_name2) == 0)
				{
					if (fc_name3 == NULL ||
						fc_nsitem->itemtype != PLPGSQL_NSTYPE_VAR)
					{
						if (fc_names_used)
							*fc_names_used = 2;
						return fc_nsitem;
					}
				}
			}
		}

		if (fc_localmode)
			break;				/* 不查看上层 */

		fc_ns_cur = fc_nsitem->prev;
	}

	/* 这只是为了抑制可能未初始化变量的警告 */
	if (fc_names_used)
		*fc_names_used = 0;
	return NULL;				/* 未找到匹配 */
}


/* ----------
 * plpgsql_ns_lookup_label		在给定的命名空间链中查找标签
 * ----------
 */
PLpgSQL_nsitem * plpgsql_ns_lookup_label(PLpgSQL_nsitem *fc_ns_cur, const char *fc_name)
{
	while (fc_ns_cur != NULL)
	{
		if (fc_ns_cur->itemtype == PLPGSQL_NSTYPE_LABEL &&
			strcmp(fc_ns_cur->name, fc_name) == 0)
			return fc_ns_cur;
		fc_ns_cur = fc_ns_cur->prev;
	}

	return NULL;				/* 标签未找到 */
}


/* ----------
 * plpgsql_ns_find_nearest_loop		在命名空间链中查找最内层循环标签
 * ----------
 */
PLpgSQL_nsitem * plpgsql_ns_find_nearest_loop(PLpgSQL_nsitem *fc_ns_cur)
{
	while (fc_ns_cur != NULL)
	{
		if (fc_ns_cur->itemtype == PLPGSQL_NSTYPE_LABEL &&
			fc_ns_cur->itemno == PLPGSQL_LABEL_LOOP)
			return fc_ns_cur;
		fc_ns_cur = fc_ns_cur->prev;
	}

	return NULL;				/* 未找到循环 */
}


/*
 * 语句类型作为字符串，用于错误消息等。
 */
const char * plpgsql_stmt_typename(PLpgSQL_stmt *fc_stmt)
{
	switch (fc_stmt->cmd_type)
	{
		case PLPGSQL_STMT_BLOCK:
			return _("statement block");
		case PLPGSQL_STMT_ASSIGN:
			return _("assignment");
		case PLPGSQL_STMT_IF:
			return "IF";
		case PLPGSQL_STMT_CASE:
			return "CASE";
		case PLPGSQL_STMT_LOOP:
			return "LOOP";
		case PLPGSQL_STMT_WHILE:
			return "WHILE";
		case PLPGSQL_STMT_FORI:
			return _("FOR with integer loop variable");
		case PLPGSQL_STMT_FORS:
			return _("FOR over SELECT rows");
		case PLPGSQL_STMT_FORC:
			return _("FOR over cursor");
		case PLPGSQL_STMT_FOREACH_A:
			return _("FOREACH over array");
		case PLPGSQL_STMT_EXIT:
			return ((PLpgSQL_stmt_exit *) fc_stmt)->is_exit ? "EXIT" : "CONTINUE";
		case PLPGSQL_STMT_RETURN:
			return "RETURN";
		case PLPGSQL_STMT_RETURN_NEXT:
			return "RETURN NEXT";
		case PLPGSQL_STMT_RETURN_QUERY:
			return "RETURN QUERY";
		case PLPGSQL_STMT_RAISE:
			return "RAISE";
		case PLPGSQL_STMT_ASSERT:
			return "ASSERT";
		case PLPGSQL_STMT_EXECSQL:
			return _("SQL statement");
		case PLPGSQL_STMT_DYNEXECUTE:
			return "EXECUTE";
		case PLPGSQL_STMT_DYNFORS:
			return _("FOR over EXECUTE statement");
		case PLPGSQL_STMT_GETDIAG:
			return ((PLpgSQL_stmt_getdiag *) fc_stmt)->is_stacked ?
				"GET STACKED DIAGNOSTICS" : "GET DIAGNOSTICS";
		case PLPGSQL_STMT_OPEN:
			return "OPEN";
		case PLPGSQL_STMT_FETCH:
			return ((PLpgSQL_stmt_fetch *) fc_stmt)->is_move ? "MOVE" : "FETCH";
		case PLPGSQL_STMT_CLOSE:
			return "CLOSE";
		case PLPGSQL_STMT_PERFORM:
			return "PERFORM";
		case PLPGSQL_STMT_CALL:
			return ((PLpgSQL_stmt_call *) fc_stmt)->is_call ? "CALL" : "DO";
		case PLPGSQL_STMT_COMMIT:
			return "COMMIT";
		case PLPGSQL_STMT_ROLLBACK:
			return "ROLLBACK";
	}

	return "unknown";
}

/*
 * 获取诊断项名称作为字符串，用于错误消息等。
 */
const char * plpgsql_getdiag_kindname(PLpgSQL_getdiag_kind fc_kind)
{
	switch (fc_kind)
	{
		case PLPGSQL_GETDIAG_ROW_COUNT:
			return "ROW_COUNT";
		case PLPGSQL_GETDIAG_CONTEXT:
			return "PG_CONTEXT";
		case PLPGSQL_GETDIAG_ERROR_CONTEXT:
			return "PG_EXCEPTION_CONTEXT";
		case PLPGSQL_GETDIAG_ERROR_DETAIL:
			return "PG_EXCEPTION_DETAIL";
		case PLPGSQL_GETDIAG_ERROR_HINT:
			return "PG_EXCEPTION_HINT";
		case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
			return "RETURNED_SQLSTATE";
		case PLPGSQL_GETDIAG_COLUMN_NAME:
			return "COLUMN_NAME";
		case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
			return "CONSTRAINT_NAME";
		case PLPGSQL_GETDIAG_DATATYPE_NAME:
			return "PG_DATATYPE_NAME";
		case PLPGSQL_GETDIAG_MESSAGE_TEXT:
			return "MESSAGE_TEXT";
		case PLPGSQL_GETDIAG_TABLE_NAME:
			return "TABLE_NAME";
		case PLPGSQL_GETDIAG_SCHEMA_NAME:
			return "SCHEMA_NAME";
	}

	return "unknown";
}


/**********************************************************************
 * 当PL/pgSQL函数不再需要时释放内存
 *
 * 递归遍历函数树的代码实际上只用于定位PLpgSQL_expr节点，这些节点可能包含
 * 引用必须释放的保存的SPI计划。函数树本身以及附属数据通过释放函数的
 * 永久内存上下文一次性释放。
 **********************************************************************/
static void fc_free_stmt(PLpgSQL_stmt *fc_stmt);
static void fc_free_block(PLpgSQL_stmt_block *fc_block);
static void fc_free_assign(PLpgSQL_stmt_assign *fc_stmt);
static void fc_free_if(PLpgSQL_stmt_if *fc_stmt);
static void fc_free_case(PLpgSQL_stmt_case *fc_stmt);
static void fc_free_loop(PLpgSQL_stmt_loop *fc_stmt);
static void fc_free_while(PLpgSQL_stmt_while *fc_stmt);
static void fc_free_fori(PLpgSQL_stmt_fori *fc_stmt);
static void fc_free_fors(PLpgSQL_stmt_fors *fc_stmt);
static void fc_free_forc(PLpgSQL_stmt_forc *fc_stmt);
static void fc_free_foreach_a(PLpgSQL_stmt_foreach_a *fc_stmt);
static void fc_free_exit(PLpgSQL_stmt_exit *fc_stmt);
static void fc_free_return(PLpgSQL_stmt_return *fc_stmt);
static void fc_free_return_next(PLpgSQL_stmt_return_next *fc_stmt);
static void fc_free_return_query(PLpgSQL_stmt_return_query *fc_stmt);
static void fc_free_raise(PLpgSQL_stmt_raise *fc_stmt);
static void fc_free_assert(PLpgSQL_stmt_assert *fc_stmt);
static void fc_free_execsql(PLpgSQL_stmt_execsql *fc_stmt);
static void fc_free_dynexecute(PLpgSQL_stmt_dynexecute *fc_stmt);
static void fc_free_dynfors(PLpgSQL_stmt_dynfors *fc_stmt);
static void fc_free_getdiag(PLpgSQL_stmt_getdiag *fc_stmt);
static void fc_free_open(PLpgSQL_stmt_open *fc_stmt);
static void fc_free_fetch(PLpgSQL_stmt_fetch *fc_stmt);
static void fc_free_close(PLpgSQL_stmt_close *fc_stmt);
static void fc_free_perform(PLpgSQL_stmt_perform *fc_stmt);
static void fc_free_call(PLpgSQL_stmt_call *fc_stmt);
static void fc_free_commit(PLpgSQL_stmt_commit *fc_stmt);
static void fc_free_rollback(PLpgSQL_stmt_rollback *fc_stmt);
static void fc_free_expr(PLpgSQL_expr *fc_expr);


static void fc_free_stmt(PLpgSQL_stmt *fc_stmt)
{
	switch (fc_stmt->cmd_type)
	{
		case PLPGSQL_STMT_BLOCK:
			fc_free_block((PLpgSQL_stmt_block *) fc_stmt);
			break;
		case PLPGSQL_STMT_ASSIGN:
			fc_free_assign((PLpgSQL_stmt_assign *) fc_stmt);
			break;
		case PLPGSQL_STMT_IF:
			fc_free_if((PLpgSQL_stmt_if *) fc_stmt);
			break;
		case PLPGSQL_STMT_CASE:
			fc_free_case((PLpgSQL_stmt_case *) fc_stmt);
			break;
		case PLPGSQL_STMT_LOOP:
			fc_free_loop((PLpgSQL_stmt_loop *) fc_stmt);
			break;
		case PLPGSQL_STMT_WHILE:
			fc_free_while((PLpgSQL_stmt_while *) fc_stmt);
			break;
		case PLPGSQL_STMT_FORI:
			fc_free_fori((PLpgSQL_stmt_fori *) fc_stmt);
			break;
		case PLPGSQL_STMT_FORS:
			fc_free_fors((PLpgSQL_stmt_fors *) fc_stmt);
			break;
		case PLPGSQL_STMT_FORC:
			fc_free_forc((PLpgSQL_stmt_forc *) fc_stmt);
			break;
		case PLPGSQL_STMT_FOREACH_A:
			fc_free_foreach_a((PLpgSQL_stmt_foreach_a *) fc_stmt);
			break;
		case PLPGSQL_STMT_EXIT:
			fc_free_exit((PLpgSQL_stmt_exit *) fc_stmt);
			break;
		case PLPGSQL_STMT_RETURN:
			fc_free_return((PLpgSQL_stmt_return *) fc_stmt);
			break;
		case PLPGSQL_STMT_RETURN_NEXT:
			fc_free_return_next((PLpgSQL_stmt_return_next *) fc_stmt);
			break;
		case PLPGSQL_STMT_RETURN_QUERY:
			fc_free_return_query((PLpgSQL_stmt_return_query *) fc_stmt);
			break;
		case PLPGSQL_STMT_RAISE:
			fc_free_raise((PLpgSQL_stmt_raise *) fc_stmt);
			break;
		case PLPGSQL_STMT_ASSERT:
			fc_free_assert((PLpgSQL_stmt_assert *) fc_stmt);
			break;
		case PLPGSQL_STMT_EXECSQL:
			fc_free_execsql((PLpgSQL_stmt_execsql *) fc_stmt);
			break;
		case PLPGSQL_STMT_DYNEXECUTE:
			fc_free_dynexecute((PLpgSQL_stmt_dynexecute *) fc_stmt);
			break;
		case PLPGSQL_STMT_DYNFORS:
			fc_free_dynfors((PLpgSQL_stmt_dynfors *) fc_stmt);
			break;
		case PLPGSQL_STMT_GETDIAG:
			fc_free_getdiag((PLpgSQL_stmt_getdiag *) fc_stmt);
			break;
		case PLPGSQL_STMT_OPEN:
			fc_free_open((PLpgSQL_stmt_open *) fc_stmt);
			break;
		case PLPGSQL_STMT_FETCH:
			fc_free_fetch((PLpgSQL_stmt_fetch *) fc_stmt);
			break;
		case PLPGSQL_STMT_CLOSE:
			fc_free_close((PLpgSQL_stmt_close *) fc_stmt);
			break;
		case PLPGSQL_STMT_PERFORM:
			fc_free_perform((PLpgSQL_stmt_perform *) fc_stmt);
			break;
		case PLPGSQL_STMT_CALL:
			fc_free_call((PLpgSQL_stmt_call *) fc_stmt);
			break;
		case PLPGSQL_STMT_COMMIT:
			fc_free_commit((PLpgSQL_stmt_commit *) fc_stmt);
			break;
		case PLPGSQL_STMT_ROLLBACK:
			fc_free_rollback((PLpgSQL_stmt_rollback *) fc_stmt);
			break;
		default:
			elog(ERROR, "unrecognized cmd_type: %d", fc_stmt->cmd_type);
			break;
	}
}

static void fc_free_stmts(List *fc_stmts)
{
	ListCell   *fc_s;

	foreach(fc_s, fc_stmts)
	{
		fc_free_stmt((PLpgSQL_stmt *) lfirst(fc_s));
	}
}

static void fc_free_block(PLpgSQL_stmt_block *fc_block)
{
	fc_free_stmts(fc_block->body);
	if (fc_block->exceptions)
	{
		ListCell   *fc_e;

		foreach(fc_e, fc_block->exceptions->exc_list)
		{
			PLpgSQL_exception *fc_exc = (PLpgSQL_exception *) lfirst(fc_e);

			fc_free_stmts(fc_exc->action);
		}
	}
}

static void fc_free_assign(PLpgSQL_stmt_assign *fc_stmt)
{
	fc_free_expr(fc_stmt->expr);
}

static void fc_free_if(PLpgSQL_stmt_if *fc_stmt)
{
	ListCell   *fc_l;

	fc_free_expr(fc_stmt->cond);
	fc_free_stmts(fc_stmt->then_body);
	foreach(fc_l, fc_stmt->elsif_list)
	{
		PLpgSQL_if_elsif *fc_elif = (PLpgSQL_if_elsif *) lfirst(fc_l);

		fc_free_expr(fc_elif->cond);
		fc_free_stmts(fc_elif->stmts);
	}
	fc_free_stmts(fc_stmt->else_body);
}

static void fc_free_case(PLpgSQL_stmt_case *fc_stmt)
{
	ListCell   *fc_l;

	fc_free_expr(fc_stmt->t_expr);
	foreach(fc_l, fc_stmt->case_when_list)
	{
		PLpgSQL_case_when *fc_cwt = (PLpgSQL_case_when *) lfirst(fc_l);

		fc_free_expr(fc_cwt->expr);
		fc_free_stmts(fc_cwt->stmts);
	}
	fc_free_stmts(fc_stmt->else_stmts);
}

static void fc_free_loop(PLpgSQL_stmt_loop *fc_stmt)
{
	fc_free_stmts(fc_stmt->body);
}

static void fc_free_while(PLpgSQL_stmt_while *fc_stmt)
{
	fc_free_expr(fc_stmt->cond);
	fc_free_stmts(fc_stmt->body);
}

static void fc_free_fori(PLpgSQL_stmt_fori *fc_stmt)
{
	fc_free_expr(fc_stmt->lower);
	fc_free_expr(fc_stmt->upper);
	fc_free_expr(fc_stmt->step);
	fc_free_stmts(fc_stmt->body);
}

static void fc_free_fors(PLpgSQL_stmt_fors *fc_stmt)
{
	fc_free_stmts(fc_stmt->body);
	fc_free_expr(fc_stmt->query);
}

static void fc_free_forc(PLpgSQL_stmt_forc *fc_stmt)
{
	fc_free_stmts(fc_stmt->body);
	fc_free_expr(fc_stmt->argquery);
}

static void fc_free_foreach_a(PLpgSQL_stmt_foreach_a *fc_stmt)
{
	fc_free_expr(fc_stmt->expr);
	fc_free_stmts(fc_stmt->body);
}

static void fc_free_open(PLpgSQL_stmt_open *fc_stmt)
{
	ListCell   *fc_lc;

	fc_free_expr(fc_stmt->argquery);
	fc_free_expr(fc_stmt->query);
	fc_free_expr(fc_stmt->dynquery);
	foreach(fc_lc, fc_stmt->params)
	{
		fc_free_expr((PLpgSQL_expr *) lfirst(fc_lc));
	}
}

static void fc_free_fetch(PLpgSQL_stmt_fetch *fc_stmt)
{
	fc_free_expr(fc_stmt->expr);
}

static void fc_free_close(PLpgSQL_stmt_close *fc_stmt)
{
}

static void fc_free_perform(PLpgSQL_stmt_perform *fc_stmt)
{
	fc_free_expr(fc_stmt->expr);
}

static void fc_free_call(PLpgSQL_stmt_call *fc_stmt)
{
	fc_free_expr(fc_stmt->expr);
}

static void fc_free_commit(PLpgSQL_stmt_commit *fc_stmt)
{
}

static void fc_free_rollback(PLpgSQL_stmt_rollback *fc_stmt)
{
}

static void fc_free_exit(PLpgSQL_stmt_exit *fc_stmt)
{
	fc_free_expr(fc_stmt->cond);
}

static void fc_free_return(PLpgSQL_stmt_return *fc_stmt)
{
	fc_free_expr(fc_stmt->expr);
}

static void fc_free_return_next(PLpgSQL_stmt_return_next *fc_stmt)
{
	fc_free_expr(fc_stmt->expr);
}

static void fc_free_return_query(PLpgSQL_stmt_return_query *fc_stmt)
{
	ListCell   *fc_lc;

	fc_free_expr(fc_stmt->query);
	fc_free_expr(fc_stmt->dynquery);
	foreach(fc_lc, fc_stmt->params)
	{
		fc_free_expr((PLpgSQL_expr *) lfirst(fc_lc));
	}
}

static void fc_free_raise(PLpgSQL_stmt_raise *fc_stmt)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_stmt->params)
	{
		fc_free_expr((PLpgSQL_expr *) lfirst(fc_lc));
	}
	foreach(fc_lc, fc_stmt->options)
	{
		PLpgSQL_raise_option *fc_opt = (PLpgSQL_raise_option *) lfirst(fc_lc);

		fc_free_expr(fc_opt->expr);
	}
}

static void fc_free_assert(PLpgSQL_stmt_assert *fc_stmt)
{
	fc_free_expr(fc_stmt->cond);
	fc_free_expr(fc_stmt->message);
}

static void fc_free_execsql(PLpgSQL_stmt_execsql *fc_stmt)
{
	fc_free_expr(fc_stmt->sqlstmt);
}

static void fc_free_dynexecute(PLpgSQL_stmt_dynexecute *fc_stmt)
{
	ListCell   *fc_lc;

	fc_free_expr(fc_stmt->query);
	foreach(fc_lc, fc_stmt->params)
	{
		fc_free_expr((PLpgSQL_expr *) lfirst(fc_lc));
	}
}

static void fc_free_dynfors(PLpgSQL_stmt_dynfors *fc_stmt)
{
	ListCell   *fc_lc;

	fc_free_stmts(fc_stmt->body);
	fc_free_expr(fc_stmt->query);
	foreach(fc_lc, fc_stmt->params)
	{
		fc_free_expr((PLpgSQL_expr *) lfirst(fc_lc));
	}
}

static void fc_free_getdiag(PLpgSQL_stmt_getdiag *fc_stmt)
{
}

static void fc_free_expr(PLpgSQL_expr *fc_expr)
{
	if (fc_expr && fc_expr->plan)
	{
		SPI_freeplan(fc_expr->plan);
		fc_expr->plan = NULL;
	}
}

void plpgsql_free_function_memory(PLpgSQL_function *fc_func)
{
	int			fc_i;

	/* 最好不要在正在使用的函数上调用此函数 */
	Assert(fc_func->use_count == 0);

	/* 释放与变量声明相关的计划 */
	for (fc_i = 0; fc_i < fc_func->ndatums; fc_i++)
	{
		PLpgSQL_datum *fc_d = fc_func->datums[fc_i];

		switch (fc_d->dtype)
		{
			case PLPGSQL_DTYPE_VAR:
			case PLPGSQL_DTYPE_PROMISE:
				{
					PLpgSQL_var *fc_var = (PLpgSQL_var *) fc_d;

					fc_free_expr(fc_var->default_val);
					fc_free_expr(fc_var->cursor_explicit_expr);
				}
				break;
			case PLPGSQL_DTYPE_ROW:
				break;
			case PLPGSQL_DTYPE_REC:
				{
					PLpgSQL_rec *fc_rec = (PLpgSQL_rec *) fc_d;

					fc_free_expr(fc_rec->default_val);
				}
				break;
			case PLPGSQL_DTYPE_RECFIELD:
				break;
			default:
				elog(ERROR, "unrecognized data type: %d", fc_d->dtype);
		}
	}
	fc_func->ndatums = 0;

	/* 释放语句树中的计划 */
	if (fc_func->action)
		fc_free_block(fc_func->action);
	fc_func->action = NULL;

	/*
	 * 最后，释放除PLpgSQL_function结构本身（必须保留，因为可能有多个
	 * fn_extra指针指向它）以外的所有内存。
	 */
	if (fc_func->fn_cxt)
		MemoryContextDelete(fc_func->fn_cxt);
	fc_func->fn_cxt = NULL;
}


/**********************************************************************
 * 用于分析编译代码的调试函数
 **********************************************************************/
static int	dump_indent;

static void fc_dump_ind(void);
static void fc_dump_stmt(PLpgSQL_stmt *fc_stmt);
static void fc_dump_block(PLpgSQL_stmt_block *fc_block);
static void fc_dump_assign(PLpgSQL_stmt_assign *fc_stmt);
static void fc_dump_if(PLpgSQL_stmt_if *fc_stmt);
static void fc_dump_case(PLpgSQL_stmt_case *fc_stmt);
static void fc_dump_loop(PLpgSQL_stmt_loop *fc_stmt);
static void fc_dump_while(PLpgSQL_stmt_while *fc_stmt);
static void fc_dump_fori(PLpgSQL_stmt_fori *fc_stmt);
static void fc_dump_fors(PLpgSQL_stmt_fors *fc_stmt);
static void fc_dump_forc(PLpgSQL_stmt_forc *fc_stmt);
static void fc_dump_foreach_a(PLpgSQL_stmt_foreach_a *fc_stmt);
static void fc_dump_exit(PLpgSQL_stmt_exit *fc_stmt);
static void fc_dump_return(PLpgSQL_stmt_return *fc_stmt);
static void fc_dump_return_next(PLpgSQL_stmt_return_next *fc_stmt);
static void fc_dump_return_query(PLpgSQL_stmt_return_query *fc_stmt);
static void fc_dump_raise(PLpgSQL_stmt_raise *fc_stmt);
static void fc_dump_assert(PLpgSQL_stmt_assert *fc_stmt);
static void fc_dump_execsql(PLpgSQL_stmt_execsql *fc_stmt);
static void fc_dump_dynexecute(PLpgSQL_stmt_dynexecute *fc_stmt);
static void fc_dump_dynfors(PLpgSQL_stmt_dynfors *fc_stmt);
static void fc_dump_getdiag(PLpgSQL_stmt_getdiag *fc_stmt);
static void fc_dump_open(PLpgSQL_stmt_open *fc_stmt);
static void fc_dump_fetch(PLpgSQL_stmt_fetch *fc_stmt);
static void fc_dump_cursor_direction(PLpgSQL_stmt_fetch *fc_stmt);
static void fc_dump_close(PLpgSQL_stmt_close *fc_stmt);
static void fc_dump_perform(PLpgSQL_stmt_perform *fc_stmt);
static void fc_dump_call(PLpgSQL_stmt_call *fc_stmt);
static void fc_dump_commit(PLpgSQL_stmt_commit *fc_stmt);
static void fc_dump_rollback(PLpgSQL_stmt_rollback *fc_stmt);
static void fc_dump_expr(PLpgSQL_expr *fc_expr);


static void fc_dump_ind(void)
{
	int			fc_i;

	for (fc_i = 0; fc_i < dump_indent; fc_i++)
		printf(" ");
}

static void fc_dump_stmt(PLpgSQL_stmt *fc_stmt)
{
	printf("%3d:", fc_stmt->lineno);
	switch (fc_stmt->cmd_type)
	{
		case PLPGSQL_STMT_BLOCK:
			fc_dump_block((PLpgSQL_stmt_block *) fc_stmt);
			break;
		case PLPGSQL_STMT_ASSIGN:
			fc_dump_assign((PLpgSQL_stmt_assign *) fc_stmt);
			break;
		case PLPGSQL_STMT_IF:
			fc_dump_if((PLpgSQL_stmt_if *) fc_stmt);
			break;
		case PLPGSQL_STMT_CASE:
			fc_dump_case((PLpgSQL_stmt_case *) fc_stmt);
			break;
		case PLPGSQL_STMT_LOOP:
			fc_dump_loop((PLpgSQL_stmt_loop *) fc_stmt);
			break;
		case PLPGSQL_STMT_WHILE:
			fc_dump_while((PLpgSQL_stmt_while *) fc_stmt);
			break;
		case PLPGSQL_STMT_FORI:
			fc_dump_fori((PLpgSQL_stmt_fori *) fc_stmt);
			break;
		case PLPGSQL_STMT_FORS:
			fc_dump_fors((PLpgSQL_stmt_fors *) fc_stmt);
			break;
		case PLPGSQL_STMT_FORC:
			fc_dump_forc((PLpgSQL_stmt_forc *) fc_stmt);
			break;
		case PLPGSQL_STMT_FOREACH_A:
			fc_dump_foreach_a((PLpgSQL_stmt_foreach_a *) fc_stmt);
			break;
		case PLPGSQL_STMT_EXIT:
			fc_dump_exit((PLpgSQL_stmt_exit *) fc_stmt);
			break;
		case PLPGSQL_STMT_RETURN:
			fc_dump_return((PLpgSQL_stmt_return *) fc_stmt);
			break;
		case PLPGSQL_STMT_RETURN_NEXT:
			fc_dump_return_next((PLpgSQL_stmt_return_next *) fc_stmt);
			break;
		case PLPGSQL_STMT_RETURN_QUERY:
			fc_dump_return_query((PLpgSQL_stmt_return_query *) fc_stmt);
			break;
		case PLPGSQL_STMT_RAISE:
			fc_dump_raise((PLpgSQL_stmt_raise *) fc_stmt);
			break;
		case PLPGSQL_STMT_ASSERT:
			fc_dump_assert((PLpgSQL_stmt_assert *) fc_stmt);
			break;
		case PLPGSQL_STMT_EXECSQL:
			fc_dump_execsql((PLpgSQL_stmt_execsql *) fc_stmt);
			break;
		case PLPGSQL_STMT_DYNEXECUTE:
			fc_dump_dynexecute((PLpgSQL_stmt_dynexecute *) fc_stmt);
			break;
		case PLPGSQL_STMT_DYNFORS:
			fc_dump_dynfors((PLpgSQL_stmt_dynfors *) fc_stmt);
			break;
		case PLPGSQL_STMT_GETDIAG:
			fc_dump_getdiag((PLpgSQL_stmt_getdiag *) fc_stmt);
			break;
		case PLPGSQL_STMT_OPEN:
			fc_dump_open((PLpgSQL_stmt_open *) fc_stmt);
			break;
		case PLPGSQL_STMT_FETCH:
			fc_dump_fetch((PLpgSQL_stmt_fetch *) fc_stmt);
			break;
		case PLPGSQL_STMT_CLOSE:
			fc_dump_close((PLpgSQL_stmt_close *) fc_stmt);
			break;
		case PLPGSQL_STMT_PERFORM:
			fc_dump_perform((PLpgSQL_stmt_perform *) fc_stmt);
			break;
		case PLPGSQL_STMT_CALL:
			fc_dump_call((PLpgSQL_stmt_call *) fc_stmt);
			break;
		case PLPGSQL_STMT_COMMIT:
			fc_dump_commit((PLpgSQL_stmt_commit *) fc_stmt);
			break;
		case PLPGSQL_STMT_ROLLBACK:
			fc_dump_rollback((PLpgSQL_stmt_rollback *) fc_stmt);
			break;
		default:
			elog(ERROR, "unrecognized cmd_type: %d", fc_stmt->cmd_type);
			break;
	}
}

static void fc_dump_stmts(List *fc_stmts)
{
	ListCell   *fc_s;

	dump_indent += 2;
	foreach(fc_s, fc_stmts)
		fc_dump_stmt((PLpgSQL_stmt *) lfirst(fc_s));
	dump_indent -= 2;
}

static void fc_dump_block(PLpgSQL_stmt_block *fc_block)
{
	char	   *fc_name;

	if (fc_block->label == NULL)
		fc_name = "*unnamed*";
	else
		fc_name = fc_block->label;

	fc_dump_ind();
	printf("BLOCK <<%s>>\n", fc_name);

	fc_dump_stmts(fc_block->body);

	if (fc_block->exceptions)
	{
		ListCell   *fc_e;

		foreach(fc_e, fc_block->exceptions->exc_list)
		{
			PLpgSQL_exception *fc_exc = (PLpgSQL_exception *) lfirst(fc_e);
			PLpgSQL_condition *fc_cond;

			fc_dump_ind();
			printf("    EXCEPTION WHEN ");
			for (fc_cond = fc_exc->conditions; fc_cond; fc_cond = fc_cond->next)
			{
				if (fc_cond != fc_exc->conditions)
					printf(" OR ");
				printf("%s", fc_cond->condname);
			}
			printf(" THEN\n");
			fc_dump_stmts(fc_exc->action);
		}
	}

	fc_dump_ind();
	printf("    END -- %s\n", fc_name);
}

static void fc_dump_assign(PLpgSQL_stmt_assign *fc_stmt)
{
	fc_dump_ind();
	printf("ASSIGN var %d := ", fc_stmt->varno);
	fc_dump_expr(fc_stmt->expr);
	printf("\n");
}

static void fc_dump_if(PLpgSQL_stmt_if *fc_stmt)
{
	ListCell   *fc_l;

	fc_dump_ind();
	printf("IF ");
	fc_dump_expr(fc_stmt->cond);
	printf(" THEN\n");
	fc_dump_stmts(fc_stmt->then_body);
	foreach(fc_l, fc_stmt->elsif_list)
	{
		PLpgSQL_if_elsif *fc_elif = (PLpgSQL_if_elsif *) lfirst(fc_l);

		fc_dump_ind();
		printf("    ELSIF ");
		fc_dump_expr(fc_elif->cond);
		printf(" THEN\n");
		fc_dump_stmts(fc_elif->stmts);
	}
	if (fc_stmt->else_body != NIL)
	{
		fc_dump_ind();
		printf("    ELSE\n");
		fc_dump_stmts(fc_stmt->else_body);
	}
	fc_dump_ind();
	printf("    ENDIF\n");
}

static void fc_dump_case(PLpgSQL_stmt_case *fc_stmt)
{
	ListCell   *fc_l;

	fc_dump_ind();
	printf("CASE %d ", fc_stmt->t_varno);
	if (fc_stmt->t_expr)
		fc_dump_expr(fc_stmt->t_expr);
	printf("\n");
	dump_indent += 6;
	foreach(fc_l, fc_stmt->case_when_list)
	{
		PLpgSQL_case_when *fc_cwt = (PLpgSQL_case_when *) lfirst(fc_l);

		fc_dump_ind();
		printf("WHEN ");
		fc_dump_expr(fc_cwt->expr);
		printf("\n");
		fc_dump_ind();
		printf("THEN\n");
		dump_indent += 2;
		fc_dump_stmts(fc_cwt->stmts);
		dump_indent -= 2;
	}
	if (fc_stmt->have_else)
	{
		fc_dump_ind();
		printf("ELSE\n");
		dump_indent += 2;
		fc_dump_stmts(fc_stmt->else_stmts);
		dump_indent -= 2;
	}
	dump_indent -= 6;
	fc_dump_ind();
	printf("    ENDCASE\n");
}

static void fc_dump_loop(PLpgSQL_stmt_loop *fc_stmt)
{
	fc_dump_ind();
	printf("LOOP\n");

	fc_dump_stmts(fc_stmt->body);

	fc_dump_ind();
	printf("    ENDLOOP\n");
}

static void fc_dump_while(PLpgSQL_stmt_while *fc_stmt)
{
	fc_dump_ind();
	printf("WHILE ");
	fc_dump_expr(fc_stmt->cond);
	printf("\n");

	fc_dump_stmts(fc_stmt->body);

	fc_dump_ind();
	printf("    ENDWHILE\n");
}

static void fc_dump_fori(PLpgSQL_stmt_fori *fc_stmt)
{
	fc_dump_ind();
	printf("FORI %s %s\n", fc_stmt->var->refname, (fc_stmt->reverse) ? "REVERSE" : "NORMAL");

	dump_indent += 2;
	fc_dump_ind();
	printf("    lower = ");
	fc_dump_expr(fc_stmt->lower);
	printf("\n");
	fc_dump_ind();
	printf("    upper = ");
	fc_dump_expr(fc_stmt->upper);
	printf("\n");
	if (fc_stmt->step)
	{
		fc_dump_ind();
		printf("    step = ");
		fc_dump_expr(fc_stmt->step);
		printf("\n");
	}
	dump_indent -= 2;

	fc_dump_stmts(fc_stmt->body);

	fc_dump_ind();
	printf("    ENDFORI\n");
}

static void fc_dump_fors(PLpgSQL_stmt_fors *fc_stmt)
{
	fc_dump_ind();
	printf("FORS %s ", fc_stmt->var->refname);
	fc_dump_expr(fc_stmt->query);
	printf("\n");

	fc_dump_stmts(fc_stmt->body);

	fc_dump_ind();
	printf("    ENDFORS\n");
}

static void fc_dump_forc(PLpgSQL_stmt_forc *fc_stmt)
{
	fc_dump_ind();
	printf("FORC %s ", fc_stmt->var->refname);
	printf("curvar=%d\n", fc_stmt->curvar);

	dump_indent += 2;
	if (fc_stmt->argquery != NULL)
	{
		fc_dump_ind();
		printf("  arguments = ");
		fc_dump_expr(fc_stmt->argquery);
		printf("\n");
	}
	dump_indent -= 2;

	fc_dump_stmts(fc_stmt->body);

	fc_dump_ind();
	printf("    ENDFORC\n");
}

static void fc_dump_foreach_a(PLpgSQL_stmt_foreach_a *fc_stmt)
{
	fc_dump_ind();
	printf("FOREACHA var %d ", fc_stmt->varno);
	if (fc_stmt->slice != 0)
		printf("SLICE %d ", fc_stmt->slice);
	printf("IN ");
	fc_dump_expr(fc_stmt->expr);
	printf("\n");

	fc_dump_stmts(fc_stmt->body);

	fc_dump_ind();
	printf("    ENDFOREACHA");
}

static void fc_dump_open(PLpgSQL_stmt_open *fc_stmt)
{
	fc_dump_ind();
	printf("OPEN curvar=%d\n", fc_stmt->curvar);

	dump_indent += 2;
	if (fc_stmt->argquery != NULL)
	{
		fc_dump_ind();
		printf("  arguments = '");
		fc_dump_expr(fc_stmt->argquery);
		printf("'\n");
	}
	if (fc_stmt->query != NULL)
	{
		fc_dump_ind();
		printf("  query = '");
		fc_dump_expr(fc_stmt->query);
		printf("'\n");
	}
	if (fc_stmt->dynquery != NULL)
	{
		fc_dump_ind();
		printf("  execute = '");
		fc_dump_expr(fc_stmt->dynquery);
		printf("'\n");

		if (fc_stmt->params != NIL)
		{
			ListCell   *fc_lc;
			int			fc_i;

			dump_indent += 2;
			fc_dump_ind();
			printf("    USING\n");
			dump_indent += 2;
			fc_i = 1;
			foreach(fc_lc, fc_stmt->params)
			{
				fc_dump_ind();
				printf("    parameter $%d: ", fc_i++);
				fc_dump_expr((PLpgSQL_expr *) lfirst(fc_lc));
				printf("\n");
			}
			dump_indent -= 4;
		}
	}
	dump_indent -= 2;
}

static void fc_dump_fetch(PLpgSQL_stmt_fetch *fc_stmt)
{
	fc_dump_ind();

	if (!fc_stmt->is_move)
	{
		printf("FETCH curvar=%d\n", fc_stmt->curvar);
		fc_dump_cursor_direction(fc_stmt);

		dump_indent += 2;
		if (fc_stmt->target != NULL)
		{
			fc_dump_ind();
			printf("    target = %d %s\n",
				   fc_stmt->target->dno, fc_stmt->target->refname);
		}
		dump_indent -= 2;
	}
	else
	{
		printf("MOVE curvar=%d\n", fc_stmt->curvar);
		fc_dump_cursor_direction(fc_stmt);
	}
}

static void fc_dump_cursor_direction(PLpgSQL_stmt_fetch *fc_stmt)
{
	dump_indent += 2;
	fc_dump_ind();
	switch (fc_stmt->direction)
	{
		case FETCH_FORWARD:
			printf("    FORWARD ");
			break;
		case FETCH_BACKWARD:
			printf("    BACKWARD ");
			break;
		case FETCH_ABSOLUTE:
			printf("    ABSOLUTE ");
			break;
		case FETCH_RELATIVE:
			printf("    RELATIVE ");
			break;
		default:
			printf("??? unknown cursor direction %d", fc_stmt->direction);
	}

	if (fc_stmt->expr)
	{
		fc_dump_expr(fc_stmt->expr);
		printf("\n");
	}
	else
		printf("%ld\n", fc_stmt->how_many);

	dump_indent -= 2;
}

static void fc_dump_close(PLpgSQL_stmt_close *fc_stmt)
{
	fc_dump_ind();
	printf("CLOSE curvar=%d\n", fc_stmt->curvar);
}

static void fc_dump_perform(PLpgSQL_stmt_perform *fc_stmt)
{
	fc_dump_ind();
	printf("PERFORM expr = ");
	fc_dump_expr(fc_stmt->expr);
	printf("\n");
}

static void fc_dump_call(PLpgSQL_stmt_call *fc_stmt)
{
	fc_dump_ind();
	printf("%s expr = ", fc_stmt->is_call ? "CALL" : "DO");
	fc_dump_expr(fc_stmt->expr);
	printf("\n");
}

static void fc_dump_commit(PLpgSQL_stmt_commit *fc_stmt)
{
	fc_dump_ind();
	if (fc_stmt->chain)
		printf("COMMIT AND CHAIN\n");
	else
		printf("COMMIT\n");
}

static void fc_dump_rollback(PLpgSQL_stmt_rollback *fc_stmt)
{
	fc_dump_ind();
	if (fc_stmt->chain)
		printf("ROLLBACK AND CHAIN\n");
	else
		printf("ROLLBACK\n");
}

static void fc_dump_exit(PLpgSQL_stmt_exit *fc_stmt)
{
	fc_dump_ind();
	printf("%s", fc_stmt->is_exit ? "EXIT" : "CONTINUE");
	if (fc_stmt->label != NULL)
		printf(" label='%s'", fc_stmt->label);
	if (fc_stmt->cond != NULL)
	{
		printf(" WHEN ");
		fc_dump_expr(fc_stmt->cond);
	}
	printf("\n");
}

static void fc_dump_return(PLpgSQL_stmt_return *fc_stmt)
{
	fc_dump_ind();
	printf("RETURN ");
	if (fc_stmt->retvarno >= 0)
		printf("variable %d", fc_stmt->retvarno);
	else if (fc_stmt->expr != NULL)
		fc_dump_expr(fc_stmt->expr);
	else
		printf("NULL");
	printf("\n");
}

static void fc_dump_return_next(PLpgSQL_stmt_return_next *fc_stmt)
{
	fc_dump_ind();
	printf("RETURN NEXT ");
	if (fc_stmt->retvarno >= 0)
		printf("variable %d", fc_stmt->retvarno);
	else if (fc_stmt->expr != NULL)
		fc_dump_expr(fc_stmt->expr);
	else
		printf("NULL");
	printf("\n");
}

static void fc_dump_return_query(PLpgSQL_stmt_return_query *fc_stmt)
{
	fc_dump_ind();
	if (fc_stmt->query)
	{
		printf("RETURN QUERY ");
		fc_dump_expr(fc_stmt->query);
		printf("\n");
	}
	else
	{
		printf("RETURN QUERY EXECUTE ");
		fc_dump_expr(fc_stmt->dynquery);
		printf("\n");
		if (fc_stmt->params != NIL)
		{
			ListCell   *fc_lc;
			int			fc_i;

			dump_indent += 2;
			fc_dump_ind();
			printf("    USING\n");
			dump_indent += 2;
			fc_i = 1;
			foreach(fc_lc, fc_stmt->params)
			{
				fc_dump_ind();
				printf("    parameter $%d: ", fc_i++);
				fc_dump_expr((PLpgSQL_expr *) lfirst(fc_lc));
				printf("\n");
			}
			dump_indent -= 4;
		}
	}
}

static void fc_dump_raise(PLpgSQL_stmt_raise *fc_stmt)
{
	ListCell   *fc_lc;
	int			fc_i = 0;

	fc_dump_ind();
	printf("RAISE level=%d", fc_stmt->elog_level);
	if (fc_stmt->condname)
		printf(" condname='%s'", fc_stmt->condname);
	if (fc_stmt->message)
		printf(" message='%s'", fc_stmt->message);
	printf("\n");
	dump_indent += 2;
	foreach(fc_lc, fc_stmt->params)
	{
		fc_dump_ind();
		printf("    parameter %d: ", fc_i++);
		fc_dump_expr((PLpgSQL_expr *) lfirst(fc_lc));
		printf("\n");
	}
	if (fc_stmt->options)
	{
		fc_dump_ind();
		printf("    USING\n");
		dump_indent += 2;
		foreach(fc_lc, fc_stmt->options)
		{
			PLpgSQL_raise_option *fc_opt = (PLpgSQL_raise_option *) lfirst(fc_lc);

			fc_dump_ind();
			switch (fc_opt->opt_type)
			{
				case PLPGSQL_RAISEOPTION_ERRCODE:
					printf("    ERRCODE = ");
					break;
				case PLPGSQL_RAISEOPTION_MESSAGE:
					printf("    MESSAGE = ");
					break;
				case PLPGSQL_RAISEOPTION_DETAIL:
					printf("    DETAIL = ");
					break;
				case PLPGSQL_RAISEOPTION_HINT:
					printf("    HINT = ");
					break;
				case PLPGSQL_RAISEOPTION_COLUMN:
					printf("    COLUMN = ");
					break;
				case PLPGSQL_RAISEOPTION_CONSTRAINT:
					printf("    CONSTRAINT = ");
					break;
				case PLPGSQL_RAISEOPTION_DATATYPE:
					printf("    DATATYPE = ");
					break;
				case PLPGSQL_RAISEOPTION_TABLE:
					printf("    TABLE = ");
					break;
				case PLPGSQL_RAISEOPTION_SCHEMA:
					printf("    SCHEMA = ");
					break;
			}
			fc_dump_expr(fc_opt->expr);
			printf("\n");
		}
		dump_indent -= 2;
	}
	dump_indent -= 2;
}

static void fc_dump_assert(PLpgSQL_stmt_assert *fc_stmt)
{
	fc_dump_ind();
	printf("ASSERT ");
	fc_dump_expr(fc_stmt->cond);
	printf("\n");

	dump_indent += 2;
	if (fc_stmt->message != NULL)
	{
		fc_dump_ind();
		printf("    MESSAGE = ");
		fc_dump_expr(fc_stmt->message);
		printf("\n");
	}
	dump_indent -= 2;
}

static void fc_dump_execsql(PLpgSQL_stmt_execsql *fc_stmt)
{
	fc_dump_ind();
	printf("EXECSQL ");
	fc_dump_expr(fc_stmt->sqlstmt);
	printf("\n");

	dump_indent += 2;
	if (fc_stmt->target != NULL)
	{
		fc_dump_ind();
		printf("    INTO%s target = %d %s\n",
			   fc_stmt->strict ? " STRICT" : "",
			   fc_stmt->target->dno, fc_stmt->target->refname);
	}
	dump_indent -= 2;
}

static void fc_dump_dynexecute(PLpgSQL_stmt_dynexecute *fc_stmt)
{
	fc_dump_ind();
	printf("EXECUTE ");
	fc_dump_expr(fc_stmt->query);
	printf("\n");

	dump_indent += 2;
	if (fc_stmt->target != NULL)
	{
		fc_dump_ind();
		printf("    INTO%s target = %d %s\n",
			   fc_stmt->strict ? " STRICT" : "",
			   fc_stmt->target->dno, fc_stmt->target->refname);
	}
	if (fc_stmt->params != NIL)
	{
		ListCell   *fc_lc;
		int			fc_i;

		fc_dump_ind();
		printf("    USING\n");
		dump_indent += 2;
		fc_i = 1;
		foreach(fc_lc, fc_stmt->params)
		{
			fc_dump_ind();
			printf("    parameter %d: ", fc_i++);
			fc_dump_expr((PLpgSQL_expr *) lfirst(fc_lc));
			printf("\n");
		}
		dump_indent -= 2;
	}
	dump_indent -= 2;
}

static void fc_dump_dynfors(PLpgSQL_stmt_dynfors *fc_stmt)
{
	fc_dump_ind();
	printf("FORS %s EXECUTE ", fc_stmt->var->refname);
	fc_dump_expr(fc_stmt->query);
	printf("\n");
	if (fc_stmt->params != NIL)
	{
		ListCell   *fc_lc;
		int			fc_i;

		dump_indent += 2;
		fc_dump_ind();
		printf("    USING\n");
		dump_indent += 2;
		fc_i = 1;
		foreach(fc_lc, fc_stmt->params)
		{
			fc_dump_ind();
			printf("    parameter $%d: ", fc_i++);
			fc_dump_expr((PLpgSQL_expr *) lfirst(fc_lc));
			printf("\n");
		}
		dump_indent -= 4;
	}
	fc_dump_stmts(fc_stmt->body);
	fc_dump_ind();
	printf("    ENDFORS\n");
}

static void fc_dump_getdiag(PLpgSQL_stmt_getdiag *fc_stmt)
{
	ListCell   *fc_lc;

	fc_dump_ind();
	printf("GET %s DIAGNOSTICS ", fc_stmt->is_stacked ? "STACKED" : "CURRENT");
	foreach(fc_lc, fc_stmt->diag_items)
	{
		PLpgSQL_diag_item *fc_diag_item = (PLpgSQL_diag_item *) lfirst(fc_lc);

		if (fc_lc != list_head(fc_stmt->diag_items))
			printf(", ");

		printf("{var %d} = %s", fc_diag_item->target,
			   plpgsql_getdiag_kindname(fc_diag_item->kind));
	}
	printf("\n");
}

static void fc_dump_expr(PLpgSQL_expr *fc_expr)
{
	printf("'%s'", fc_expr->query);
}

void plpgsql_dumptree(PLpgSQL_function *fc_func)
{
	int			fc_i;
	PLpgSQL_datum *fc_d;

	printf("\nExecution tree of successfully compiled PL/pgSQL function %s:\n",
		   fc_func->fn_signature);

	printf("\nFunction's data area:\n");
	for (fc_i = 0; fc_i < fc_func->ndatums; fc_i++)
	{
		fc_d = fc_func->datums[fc_i];

		printf("    entry %d: ", fc_i);
		switch (fc_d->dtype)
		{
			case PLPGSQL_DTYPE_VAR:
			case PLPGSQL_DTYPE_PROMISE:
				{
					PLpgSQL_var *fc_var = (PLpgSQL_var *) fc_d;

					printf("VAR %-16s type %s (typoid %u) atttypmod %d\n",
						   fc_var->refname, fc_var->datatype->typname,
						   fc_var->datatype->typoid,
						   fc_var->datatype->atttypmod);
					if (fc_var->isconst)
						printf("                                  CONSTANT\n");
					if (fc_var->notnull)
						printf("                                  NOT NULL\n");
					if (fc_var->default_val != NULL)
					{
						printf("                                  DEFAULT ");
						fc_dump_expr(fc_var->default_val);
						printf("\n");
					}
					if (fc_var->cursor_explicit_expr != NULL)
					{
						if (fc_var->cursor_explicit_argrow >= 0)
							printf("                                  CURSOR argument row %d\n", fc_var->cursor_explicit_argrow);

						printf("                                  CURSOR IS ");
						fc_dump_expr(fc_var->cursor_explicit_expr);
						printf("\n");
					}
					if (fc_var->promise != PLPGSQL_PROMISE_NONE)
						printf("                                  PROMISE %d\n",
							   (int) fc_var->promise);
				}
				break;
			case PLPGSQL_DTYPE_ROW:
				{
					PLpgSQL_row *fc_row = (PLpgSQL_row *) fc_d;
					int			fc_i;

					printf("ROW %-16s fields", fc_row->refname);
					for (fc_i = 0; fc_i < fc_row->nfields; fc_i++)
					{
						printf(" %s=var %d", fc_row->fieldnames[fc_i],
							   fc_row->varnos[fc_i]);
					}
					printf("\n");
				}
				break;
			case PLPGSQL_DTYPE_REC:
				printf("REC %-16s typoid %u\n",
					   ((PLpgSQL_rec *) fc_d)->refname,
					   ((PLpgSQL_rec *) fc_d)->rectypeid);
				if (((PLpgSQL_rec *) fc_d)->isconst)
					printf("                                  CONSTANT\n");
				if (((PLpgSQL_rec *) fc_d)->notnull)
					printf("                                  NOT NULL\n");
				if (((PLpgSQL_rec *) fc_d)->default_val != NULL)
				{
					printf("                                  DEFAULT ");
					fc_dump_expr(((PLpgSQL_rec *) fc_d)->default_val);
					printf("\n");
				}
				break;
			case PLPGSQL_DTYPE_RECFIELD:
				printf("RECFIELD %-16s of REC %d\n",
					   ((PLpgSQL_recfield *) fc_d)->fieldname,
					   ((PLpgSQL_recfield *) fc_d)->recparentno);
				break;
			default:
				printf("??? unknown data type %d\n", fc_d->dtype);
		}
	}
	printf("\nFunction's statements:\n");

	dump_indent = 0;
	printf("%3d:", fc_func->action->lineno);
	fc_dump_block(fc_func->action);
	printf("\nEnd of execution tree of function %s\n\n", fc_func->fn_signature);
	fflush(stdout);
}
